/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.aop.aspectj;

import java.io.Serializable;
import java.lang.reflect.Method;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.weaver.tools.JoinPointMatch;

import org.springframework.aop.ProxyMethodInvocation;

/**
 * AspectJAroundAdvice 是 Spring AOP 对 AspectJ @Around 注解的封装实现。
 * 它是一个环绕通知（Around Advice），可以在方法执行前后自定义逻辑。
 *
 * - 实现了 MethodInterceptor 接口：Spring AOP 的核心调用接口，用于创建代理方法的拦截器。
 * - 继承 AbstractAspectJAdvice：封装了切面方法、切入点(Pointcut)和切面实例工厂(AspectInstanceFactory)。
 *
 * Spring AOP 通过 Advisor + Advice + Pointcut 机制来实现 AOP：
 * - Advisor：AspectJPointcutAdvisor，将 Advice + Pointcut 封装
 * - Advice：AspectJAroundAdvice 是一种具体的 Advice
 * - Pointcut：AspectJExpressionPointcut 决定通知生效的目标方法
 *
 * 功能核心：
 * - 在代理方法被调用时，生成 ProceedingJoinPoint
 * - 调用用户自定义的 @Around 方法
 * - 可以控制目标方法执行（可选择调用或不调用目标方法）
 *
 * 举例：
 * @Aspect
 * public class LogAspect {
 *     @Around("execution(* com.example.service.*.*(..))")
 *     public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
 *         System.out.println("Before method: " + pjp.getSignature().getName());
 *         Object result = pjp.proceed(); // 执行目标方法
 *         System.out.println("After method: " + pjp.getSignature().getName());
 *         return result;
 *     }
 * }
 *
 * Spring 在创建代理对象时，会将这个方法封装为 AspectJAroundAdvice，然后放入 Advisor -> 增强链(Advisor数组)。
 * 当代理方法被调用时，增强链会依次执行环绕通知、前置通知、目标方法、后置通知等。
 *
 * Spring AOP around advice (MethodInterceptor) that wraps
 * an AspectJ advice method. Exposes ProceedingJoinPoint.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @since 2.0
 */
@SuppressWarnings("serial")
public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {

	/**
	 * 构造函数
	 * @param aspectJAroundAdviceMethod 切面中的 @Around 方法
	 * @param pointcut 切入点对象，决定哪些方法需要增强
	 * @param aif 切面实例工厂，用于获取切面对象
	 */
	public AspectJAroundAdvice(
			Method aspectJAroundAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {

		super(aspectJAroundAdviceMethod, pointcut, aif);
	}

	/**
	 * 是否为前置通知
	 */
	@Override
	public boolean isBeforeAdvice() {
		return false;
	}

	/**
	 * 是否为后置通知
	 */
	@Override
	public boolean isAfterAdvice() {
		return false;
	}

	/**
	 * Around Advice 支持 ProceedingJoinPoint
	 * @return true
	 */
	@Override
	protected boolean supportsProceedingJoinPoint() {
		return true;
	}

	/**
	 * 核心方法：代理方法调用时执行
	 * @param mi MethodInvocation 代理方法调用对象
	 * @return 用户方法执行结果
	 */
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// 必须是 Spring 的 ProxyMethodInvocation 类型
		if (!(mi instanceof ProxyMethodInvocation)) {
			throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
		}
		ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
		// 生成 ProceedingJoinPoint（懒加载）
		ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
		// 获取 JoinPointMatch（匹配点信息）
		JoinPointMatch jpm = getJoinPointMatch(pmi);
		// 调用切面方法
		return invokeAdviceMethod(pjp, jpm, null, null);
	}

	/**
	 * 生成 ProceedingJoinPoint 对象
	 * 这个对象会传给 @Around 方法，用户可以调用 pjp.proceed() 执行目标方法
	 *
	 * Return the ProceedingJoinPoint for the current invocation,
	 * instantiating it lazily if it hasn't been bound to the thread already.
	 * @param rmi the current Spring AOP ReflectiveMethodInvocation,
	 * which we'll use for attribute binding
	 * @return the ProceedingJoinPoint to make available to advice methods
	 */
	protected ProceedingJoinPoint lazyGetProceedingJoinPoint(ProxyMethodInvocation rmi) {
		return new MethodInvocationProceedingJoinPoint(rmi);
	}

}